import sys
parent_dir = "../pygl"
sys.path.append(parent_dir)
import OpenGL.GL as gl
import glm
import imgui
from glfw import _GLFWwindow as GLFWwindow
import numpy as np
import pygl
from common import App
import time
import glfw

class Bezier2DComponent:
    def __init__(self, data:np.ndarray, color:glm.vec3) -> None:
        self.vao = pygl.VertexArrayObject()
        self.binding_point = 0
        self.attribute_position = pygl.VertexAttribute("position", 0, 3, 
                                                  gl.GL_FLOAT, False, 0)
        self.vbo = pygl.VertexBufferObject(data)
        self.vao.setVertexBuffer(self.vbo, self.binding_point, 0, 
                                 3 * gl.sizeof(gl.GLfloat))
        self.vao.setVertexAttribute(self.binding_point, 
                                          self.attribute_position)
        self.color = color
    
    def setData(self, data:np.ndarray)->None:
        self.vbo.setData(0, data)

    def render(self, program:pygl.Program, count:int)->None:
        self.vao.bind()
        program.setUniform3fv(0, self.color)
        self.renderComponent(count)

    def renderComponent(self, count:int)->None:
        pass

    def delete(self)->None:
        self.vao.delete()
        self.vbo.delete()

class Bezier2DCurve(Bezier2DComponent):
    def renderComponent(self, count:int)->None:
        gl.glPointSize(4.0)
        gl.glDrawArrays(gl.GL_POINTS, count, 1)
        gl.glDrawArrays(gl.GL_LINE_STRIP, 0, count + 1)

class Bezier2DControlPoints(Bezier2DComponent):
    def renderComponent(self, count:int)->None:
        gl.glPointSize(10.0)
        gl.glDrawArrays(gl.GL_POINTS, 0, count)

class Bezier2DControlPolygon(Bezier2DComponent):
    def renderComponent(self, count:int)->None:
        gl.glDrawArrays(gl.GL_LINE_STRIP, 0, count)

class Bezier2DIntermediatePolygon(Bezier2DComponent):
    def renderComponent(self, count:int)->None:
        gl.glPointSize(4.0)
        gl.glDrawArrays(gl.GL_POINTS, 0, count)
        gl.glDrawArrays(gl.GL_LINE_STRIP, 0, count)

class Bezier2DApp(App):
    def de_casteljau(self, control_points:list, t:float, is_drawing=False) -> list:
        number = len(control_points)
        if number == 1:
            return control_points[0]
        new_points = []
        for i in range(number - 1):
            new_point = (1 - t) * control_points[i] + t * control_points[i + 1]
            new_points.append(new_point)
        # 如果是用于绘制，需要把中间辅助线数据传入GPU
        if is_drawing:
            if number > 2:
                self.bezier_intermediate_polygon[self.num_control_points - 
                                                 number].setData(
                    np.array(new_points))
        return self.de_casteljau(new_points, t,is_drawing)

    # 初始化着色器，vao vbo等
    def setupVertex(self) -> None:
        self.program = pygl.ProgramVF("intermediate/shaders/bezier2d.vs", 
                                      "intermediate/shaders/bezier2d.fs")
        self.background_color = glm.vec4(0.3, 0.3, 0.3, 1.0)
        self.control_points = [
            glm.vec3(-0.4, 0.1, 0.0),
            glm.vec3(-0.1, -0.9, 0.0),
            glm.vec3(0.4 , -0.9, 0.0),
            glm.vec3(0.7, 0.1, 0.0)
        ]
        self.num_control_points = len(self.control_points)
        self.step = 101
        self.t_values = np.linspace(0, 1, self.step)

        # 先创建辅助线
        self.bezier_intermediate_polygon:list[Bezier2DIntermediatePolygon] = []
        self.intermediate_polygon_color = glm.vec3(0.0, 1.0, 0.0)
        for i in range(self.num_control_points - 2):
            data = [glm.vec3(0.0) for j in range(self.num_control_points - 1 - i)]
            self.bezier_intermediate_polygon.append(
                Bezier2DIntermediatePolygon(np.array(data), 
                self.intermediate_polygon_color))
        # 创建贝塞尔曲线数据
        self.vertices = [self.de_casteljau(self.control_points, t) 
                         for t in self.t_values]
        self.bezier_curve = Bezier2DCurve(np.array(self.vertices), 
                                          glm.vec3(1.0, 0.0, 0.0))
        self.bezier_control_points = Bezier2DControlPoints(np.array(
            self.control_points), glm.vec3(0.0, 0.0, 1.0))
        self.bezier_control_polygon = Bezier2DControlPolygon(np.array(
            self.control_points), glm.vec3(1.0, 1.0, 1.0))

        # 显示模式 0 是动画模式， 1 是 步进模式
        self.show_mode = 1
        self.t_index = 50
        self.imgui_window_width = 380
        self.imgui_window_height = 262

        # 判断是否选中了某个控制点
        self.index_modifying = -1
        self.epsilon = 0.02

    def changeBezierCurve(self)->None:
        self.vertices = [self.de_casteljau(self.control_points, t, True)
                          for t in self.t_values]
        self.bezier_curve.setData(np.array(self.vertices))
        self.bezier_control_points.setData(np.array(self.control_points))
        self.bezier_control_polygon.setData(np.array(self.control_points))

    def drawBezierCurve(self, index : int)->None:
        # 绘制控制点
        self.bezier_control_points.render(self.program, self.num_control_points)
        # 绘制控制多边形
        self.bezier_control_polygon.render(self.program, self.num_control_points)
        # 绘制各阶辅助线
        t = self.t_values[index]
        self.de_casteljau(self.control_points, t, True)
        for i in range(self.num_control_points - 2):
            self.bezier_intermediate_polygon[i].render(
                self.program, self.num_control_points - 1 - i)
        # 绘制贝塞尔曲线
        self.bezier_curve.render(self.program, index)

    # 配置imgui界面元素
    def showImGui(self)->None:
        imgui.begin("设置")
        _, color = imgui.color_edit3("背景颜色", self.background_color.x, 
                                     self.background_color.y, 
                                     self.background_color.z)
        self.background_color = glm.vec4(color[0], color[1], color[2], 1.0)
        _, color = imgui.color_edit3("控制点颜色", self.bezier_control_points.color.x, 
            self.bezier_control_points.color.y, self.bezier_control_points.color.z)
        self.bezier_control_points.color = glm.vec3(color[0], color[1], color[2])
        _, color = imgui.color_edit3("控制多边形颜色", 
                                     self.bezier_control_polygon.color.x, 
            self.bezier_control_polygon.color.y, self.bezier_control_polygon.color.z)
        self.bezier_control_polygon.color = glm.vec3(color[0], color[1], color[2])
        _, color = imgui.color_edit3("贝塞尔曲线颜色", self.bezier_curve.color.x, 
                    self.bezier_curve.color.y, self.bezier_curve.color.z)
        self.bezier_curve.color = glm.vec3(color[0], color[1], color[2])
        _, color = imgui.color_edit3("辅助线颜色", self.intermediate_polygon_color.x, 
                    self.intermediate_polygon_color.y, 
                    self.intermediate_polygon_color.z)
        self.intermediate_polygon_color = glm.vec3(color[0], color[1], color[2])
        for i in range(self.num_control_points - 2):
            self.bezier_intermediate_polygon[i].color =  \
                self.intermediate_polygon_color
        if imgui.radio_button("动画模式", self.show_mode == 0):
            self.show_mode = 0
        if imgui.radio_button("步进模式", self.show_mode == 1):
            self.show_mode = 1
        if self.show_mode == 1:
            imgui.text("鼠标拖动改变t值")
            _, self.t_index = imgui.slider_int("t %", self.t_index, 0, self.step - 1)
        imgui.end()       

    # 渲染图形
    def render(self) -> None:
        gl.glClearBufferfv(gl.GL_COLOR, 0, self.background_color.to_tuple())
        gl.glClearBufferfv(gl.GL_DEPTH, 0, 1.0)
        self.program.use()
        if self.show_mode == 0:
            self.drawBezierCurve(self.t_index)
            if self.t_index == self.step - 1:
                self.t_index = 0
            else:
                self.t_index += 1
            # 动画模式时控制渲染速度
            sleep = True
            if sleep:
                time.sleep(1.0/30)            
                glfw.poll_events()
                sleep = False
        else:
            self.drawBezierCurve(self.t_index)

    # 判断是否靠近某个控制点
    def closeToControlPoint(self, a:float, b:float, epsilon:float)->bool:
        result = abs(a - b) <= epsilon
        return result

    def mouse_move(self, window:GLFWwindow, x:float, y:float)->None:
        if imgui.is_any_item_active():
            return
        if glfw.get_mouse_button(self.window , glfw.MOUSE_BUTTON_LEFT) \
                                == glfw.PRESS:
            xpos = x / self.width * 2.0 - 1.0
            ypos = -y / self.height * 2.0 + 1.0 
            for i in range(self.num_control_points):
                check_x = self.closeToControlPoint(self.control_points[i].x, 
                                           xpos, self.epsilon)
                check_y = self.closeToControlPoint(self.control_points[i].y, 
                                           ypos, self.epsilon)
                if check_x and check_y:
                    self.index_modifying = i
            if self.index_modifying != -1:
                self.control_points[self.index_modifying] = glm.vec3(xpos, ypos, 0.0)
                self.changeBezierCurve()

    # 释放资源
    def cleanup(self) -> None:
        self.program.delete()
        self.bezier_curve.delete()
        self.bezier_control_points.delete()
        self.bezier_control_polygon.delete()
        for i in range(self.num_control_points - 2):
            self.bezier_intermediate_polygon[i].delete()

if __name__ == "__main__":
    app = Bezier2DApp(100, 100, 1000, 600, "你好，二维贝塞尔曲线App！")
    app.run()


